package com.example.wujinapi.comm.wechat;

import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 微信付款返回数据工具类
 *
 * @auther fub
 * @date 2021-06-25 15:55
 */
public class WxPayUtils {


    /**
     * 用微信V3密钥解密响应体.
     *
     * @param associatedData response.body.data[i].encrypt_certificate.associated_data
     * @param nonce          response.body.data[i].encrypt_certificate.nonce
     * @param ciphertext     response.body.data[i].encrypt_certificate.ciphertext
     * @return the string
     * @throws GeneralSecurityException the general security exception
     */
    public static String decryptResponseBody(String associatedData, String nonce, String ciphertext, String v3Key) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(v3Key.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));

            byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 验证微信签名 直连模式
     *
     * @param request  request
     * @param body     请求参数body
     * @param wxConfig 微信配置
     * @return 验签是否成功
     */
    public static boolean verifiedSignDirect(HttpServletRequest request, String body, Map<String, String> wxConfig) {
        try {
            //微信返回的证书序列号
//        String serialNo = request.getHeader("Wechatpay-Serial");
            //微信返回的随机字符串
            String nonceStr = request.getHeader("Wechatpay-Nonce");
            //微信返回的时间戳
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            //微信返回的签名
            String wechatSign = request.getHeader("Wechatpay-Signature");
            //组装签名字符串
            String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
            //根据序列号获取平台证书
            AutoUpdateCertificatesVerifier wxVerifier = WxSignUtils.getWxVerifier(wxConfig.get("pay_sh_num"), WxSignUtils.getApiSerialNumber(WxConstants.API_CLIENT_MERCHANT_CERT_PATH), WxSignUtils.getApiPrivateKey(WxConstants.API_CLIENT_MERCHANT_KEY_PATH), wxConfig.get("pay_sh_secret"));
            X509Certificate certificate = wxVerifier.getValidCertificate();
            //获取失败 验证失败
            if (certificate == null) {
                return false;
            }
            //SHA256withRSA签名
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initVerify(certificate);
            signature.update(signStr.getBytes());
            //返回验签结果
            return signature.verify(Base64Utils.decodeFromString(wechatSign));
        } catch (Exception e) {
            return false;
        }
    }
    /**
     * 验证微信签名 服务商
     *
     * @param request  request
     * @param body     请求参数body
     * @param wxConfig 微信配置
     * @return 验签是否成功
     */
    public static boolean verifiedSign(HttpServletRequest request, String body, Map<String, String> wxConfig) {
        try {
            //微信返回的证书序列号
//        String serialNo = request.getHeader("Wechatpay-Serial");
            //微信返回的随机字符串
            String nonceStr = request.getHeader("Wechatpay-Nonce");
            //微信返回的时间戳
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            //微信返回的签名
            String wechatSign = request.getHeader("Wechatpay-Signature");
            //组装签名字符串
            String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
            //根据序列号获取平台证书
            AutoUpdateCertificatesVerifier wxVerifier = WxSignUtils.getWxVerifier(wxConfig.get("pay_service_num"), WxSignUtils.getApiSerialNumber(WxConstants.API_CLIENT_SERVER_CERT_PATH), WxSignUtils.getApiPrivateKey(WxConstants.API_CLIENT_SERVER_KEY_PATH), wxConfig.get("pay_service_secret"));
            X509Certificate certificate = wxVerifier.getValidCertificate();
            //获取失败 验证失败
            if (certificate == null) {
                return false;
            }
            //SHA256withRSA签名
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initVerify(certificate);
            signature.update(signStr.getBytes());
            //返回验签结果
            return signature.verify(Base64Utils.decodeFromString(wechatSign));
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取请求文体
     *
     * @param request request
     * @return {@link String}
     * @throws IOException
     */
    public static String getRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream stream = null;
        BufferedReader reader = null;
        StringBuffer sb = new StringBuffer();
        try {
            stream = request.getInputStream();
            // 获取响应
            reader = new BufferedReader(new InputStreamReader(stream));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new IOException("读取返回支付接口数据流出现异常！");
        } finally {
            reader.close();
        }
        return sb.toString();
    }
}
